﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.Linq.Expressions;
using System.Reflection;

namespace NeoPatterns.Utility
{
    /// <summary>
    /// What is extreme laziness? :)
    /// This class allows to get PropertyInfo or MethodInfo given a lambda (which allows to refactor members)
    /// </summary>
    public static class ReflectionUtility
    {
        private static MethodInfo GetMethodInfoFromLambda(LambdaExpression lambdaExpression)
        {
            var methodCallExpression = lambdaExpression.Body as MethodCallExpression;
            if (methodCallExpression != null)
                return methodCallExpression.Method;
            throw new ArgumentException("Lambda expression is not correctly formated for MethodInfo extraction");
        }

        /// <summary>
        /// Returns a method info, providing a lambda
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static MethodInfo GetMethodInfo<T>(this Expression<Action<T>> lambdaExpression)
        {
            return GetMethodInfoFromLambda(lambdaExpression);
        }

        /// <summary>
        /// Returns a method info, for example ReflectionUtility.GetMethodInfo(() => A.F()) would return "A.F()" MethodInfo
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static MethodInfo GetMethodInfo(this Expression<Action> lambdaExpression)
        {
            return GetMethodInfoFromLambda(lambdaExpression);
        }

        /// <summary>
        /// Extracts a MemberInfo with more or less digging
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static MemberInfo GetMemberInfoFromExpression(Expression expression)
        {
            switch (expression.NodeType)
            {
            // the ReflectionUtility Get** methods return the value as a object. If the value is a struct, we get a cast,
            // that we must unwrap
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                return GetMemberInfoFromExpression(((UnaryExpression)expression).Operand);
            case ExpressionType.MemberAccess:
                return ((MemberExpression)expression).Member;
            default:
                throw new ArgumentException("Lambda expression is not correctly formated for MemberInfo extraction");
            }
        }

        /// <summary>
        /// Returns MemberInfo specified in the lambda
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        private static MemberInfo GetMemberInfoFromLambda(LambdaExpression lambdaExpression)
        {
            return GetMemberInfoFromExpression(lambdaExpression.Body);
        }

        /// <summary>
        /// Returns a PropertyInfo, given a lambda
        /// For example: ReflectionUtility.GetPropertyInfo&lt;A>(a => a.Prop) would return PropertyInfo "A.Prop"
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static PropertyInfo GetPropertyInfo<T>(this Expression<Func<T, object>> lambdaExpression)
        {
            return (PropertyInfo)GetMemberInfoFromLambda(lambdaExpression);
        }

        /// <summary>
        /// Returns a PropertyInfo, given a lambda
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static PropertyInfo GetPropertyInfo(this Expression<Func<object>> lambdaExpression)
        {
            return (PropertyInfo)GetMemberInfoFromLambda(lambdaExpression);
        }

        /// <summary>
        /// Returns a MemberInfo, given a lambda
        /// For example: ReflectionUtility.GetMemberInfo&lt;A>(a => a.Prop) would return MemberInfo "A.Prop"
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static MemberInfo GetMemberInfo<T>(this Expression<Func<T, object>> lambdaExpression)
        {
            return GetMemberInfoFromLambda(lambdaExpression);
        }

        /// <summary>
        /// Returns a MemberInfo, given a lambda
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <returns></returns>
        public static MemberInfo GetMemberInfo(this Expression<Func<object>> lambdaExpression)
        {
            return GetMemberInfoFromLambda(lambdaExpression);
        }
    }
}
